Classic Menus "If gold rust what shall iren do?" Chaucer 1340-1400 Introduction A classic menu is a menu displayed in a window (as shown in figure 10.1 below). In applications that don't use a desktop (like Quicken) the classic menu often forms the main screen of the program. The GOLDMENU unit provides pop-up menus with the following features: - Automatic prefixing of options with numbers, letters or function keys. - Support for disabled options. - Automatic menu placement with multiple column support. - Full mouse support. Figure 10.1 A Classic Menu Basic Concepts GOLDMENU defines the MenuRecord record as follows: MenuRecord = record Heading1 : string[MenuStrLength]; Heading2 : string[MenuStrLength]; Topic : array[1..MaxChoices] of string[MenuStrLength]; TotalPicks : integer; PicksPerLine : byte; AddPrefix : byte; TopLeftXY : array[1..2] of byte; Boxtype : byte; Colors : array[1..5] of byte; Margins : byte; AllowEsc : boolean; Hook : MenuHook; HindHook : MenuHindHook; end; {MenuRecord} A menu record defines all the properties of the menu, i.e. the headings, the colors, the position, the menu items, etc. The basic approach to displaying a menu is to declare a variable of type MenuRecord, update the record variable with the menu items, headings etc., and call DisplayMenu to instruct Gold to display the menu and return when the user has made a selection. Listed below is an extract from the demo program DEMMEN1.PAS which illustrates this basic approach: var MainMenu: MenuRecord; Choice,Ecode: integer; procedure SetMenu; {} begin MenuSet(MainMenu); with MainMenu do begin Heading1 := 'TechnoJock''s Turbo Toolkit'; Heading2 := 'Gold!'; Topic[1] := 'Unit Descriptions'; Topic[2] := '!Unit Demos'; Topic[3] := 'Self-Running Demo'; Topic[4] := 'How to register'; Topic[5] := 'About GOLD'; Topic[6] := ''; Topic[7] := 'Exit Demo'; TotalPicks := 7; Boxtype := 5; AddPrefix := 3; PicksPerLine := 1; AllowEsc := false; end; end; { SetMenu } begin ... SetMenu; DisplayMenu(MainMenu,false,Choice,ECode); ... end. Use MenuSet First! Important Note: Always, always, call the MenuSet function to initialize a menu record before assigning values to the various menu record components. MenuSet initializes all the menu record elements to suitable default values. Managing the MenuRecord Listed below is a description of the main MenuRecord fields: Heading1 - The heading at the top of the menu. Set the string to null (the default) if you don't want a heading. Heading2 - The second heading at the top of the menu. Set the string to null (the default) if you don't want a heading. Topic - An array of the actual menu items that will be displayed in the menu. Start with the first element, assign menu items strings to each element of the array until you have fully defined the menu items, e.g. Topic[1] := 'Unit Descriptions'; Topic[2] := '-Unit Demos'; Topic[3] := 'Self-Running Demo'; Topic[4] := 'How to register'; Topic[5] := 'About GOLD'; Topic[6] := ''; Topic[7] := 'Exit Demo'; TotalPicks - The total number of items in the menu. In the above example, the value would be 7. PicksPerLine - Identifies how many items will be displayed on each line in the menu. Gold automatically adjusts this setting if the text is too long to fit on the screen. AddPrefix - Gold can automatically prefix the menu items with numbers, letters or function keys. By default, the prefix is zero which indicates that no prefix should be used. The other valid settings are 1, 2, 3 and 4 as follows: 1 All menu options are prefixed with a single digit number. If TotalPicks exceeds 9, an alpha prefix will be used instead. 2 All menu options are prefixed with a letter, starting with A. If TotalPicks exceeds 26, the prefix is ignored. 3 All menu options are prefixed with a function key, beginning with F1. If total picks exceeds 10, the Gold will prefix with alphas instead. 4 The first capital letter in each item is highlighted and the user can select the item by pressing the highlighted letter. TopLeftXY - A two byte array which identifies the X and Y coordinate for the top left corner of the menu -- Gold automatically determines the overall dimensions of the menu based upon the length of headings and items. If you specify coordinates too close to a screen edge, Gold automatically adjusts the menu position so that it is fully visible. When the X coordinate is set to 0, the menu is centered horizontally on the display. Similarly, a Y coordinate 0 will center the menu vertically. For example, if you want the menu to be displayed in the center of the screen starting on the seventh line, the declaration would be: MainMenu.TopLeftXY[1] := 0; MainMenu.TopLeftXY[2] := 7; BoxType - Indicates the window style, and should have a value in the range 0 to 2 or 5, as follows: 0 No Border 1 Single Line Border 2 Double Line Border 5 Quicken-style menu Colors - A five byte array which defines the menu display colors. Refer to the section Customizing Menu Colors (later) for more information. Margins - The number of spaces separating the menu border and the picks. Typical values range from 1 to 5. AllowEsc - A boolean variable which controls whether the Esc key is operative, i.e. whether or not the user can escape from the menu. When this field is set to FALSE, the escape key is ignored, otherwise the menu session finishes and the Retcode is set to 1. A Few Tips and Tricks If a menu topic begins with the exclamation character (!), the menu item will be inactive, i.e. grayed out. If you want a blank line between two specific items, specify an item in between the two using a null string -- this approach is used in DEMMEN1.PAS on element 6. Don't forget that the menu will be automatically centered if the TopLeftXY[1] and TopLeftXY[2] elements are set to zero. By default, Gold supports a maximum of 30 items in a menu. If you need more than 30, just assign a higher value to the GOLDMENU constant MaxChoices. Displaying the Menu Having called MenuSet and configured the menu record, use DisplayMenu (defined below) to display the menu. DisplayMenu(MenuDef: MenuRecord; Window:Boolean; var Choice,Errorcode: integer); Displays a classic menu. MenuDef is the menu record defining the menu content. Set Window to true if you want the menu to be removed when a selection is made, or false if you want the menu to remain visible. The Choice variable is updated with the user's selection. Error codes are returned in the ErrorCode variable. Controlling the Default Menu Selection The third parameter passed to DisplayMenu is a variable integer parameter which is updated with the selection made by the user, e.g. it will have a value of 7 if the user selects the seventh menu option. This variable also controls which menu item is highlighted when the menu is first displayed. Be sure to assign an appropriate value to the Choice variable before calling DisplayMenu. Checking the Return Code If the user is allowed to escape (by setting the menu record field AllowEsc to TRUE), you will need to test the value of the ErrorCode parameter before launching the appropriate menu selection. If the user escaped from the menu, ErrorCode will be set to 1, otherwise it will have a value of zero. Customizing the Menu Colors The default menu colors assigned to each individual menu can be set using the GoldSetColor procedure, assigning attributes to the following TINT elements: MenuHiHot MenuHi MenuNormHot MenuNorm MenuBorder The following code is an extract from DEMMEN2.PAS which assigns custom colors to the menu defaults: procedure CustomizeColors; {} begin GoldSetColor(MenuHiHot,YellowOnGreen); GoldSetColor(MenuHi,BlackonGreen); GoldSetColor(MenuNormHot,YellowOnMagenta); GoldSetColor(MenuNorm,WhiteOnMagenta); GoldSetColor(MenuOff,LightgrayonMagenta); GoldSetColor(MenuBorder,YellowOnMagenta); end; { CustomizeColors } The GoldSetColor procedure should be called prior to initializing the menu record with MenuSet. The technique just described controls the default colors for all menus. You can change the colors for an individual menu by modifying the values of the menu record's Colors array, after initializing the record with MenuSet. Each of the five elements of the array control the display colors of the menu as follows: Colors[1] The highlighted item's prefix. Colors[2] The text of the highlighted item. Colors[3] The menu headings and the standard items prefix. Colors[4] The text of the standard items. Colors[5] The menu border color. The color of non-selectable items (i.e. items which start with the minus character) can only be altered by using the GoldSetColor method (described above) using the MenuOff element. Using Menu Hooks Classic menus support two types of hooks: hind hooks and character hooks: Using Hind Hooks The hind hook is called once when the menu is first displayed, and every time a keystroke or mouse click is processed by the menu. A hind hook is ideal for displaying a long description for the highlighted topic. For a procedure to be eligible as a pop-up menu hind hook it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with two passed parameters. The first integer parameter indicates the active item. The second parameter must be a variable of type integer; if this variable is assigned a non-zero value, the menu will automatically terminate, and the Errorcode (passed to DisplayMenu) will be assigned the value. The following procedure declaration follows these rules: {$F+} procedure LongDesc(Choice:integer; var Ecode:integer); {Displays a long description for the highlighted topic} var Msg: StrScreen; begin ClearLine(24,WhiteOnBlue); case Choice of 1: Msg := 'Provides descriptions of each unit'; 2: Msg := 'Demos showing the power of each unit'; 3: Msg := 'Launches into the self-running demo'; 4: Msg := 'Explains how you can register Gold'; 5: Msg := 'Displays program information'; 7: Msg := 'Stops the program'; end; WriteCenter(24,UseTint,Msg); end; { LongDesc } {$F-} Having written a procedure following the rules, the menu record field HindHook should be set to the procedure, e.g. MainMenu.HindHook := LongDesc; The demo program DEMMEN3.PAS illustrates how to use a hind hook. Using Character Hooks A classic menu character hook is a procedure which is called every time a key is pressed. The hooked procedure is called before the key is processed, i.e. before the character is handled by DisplayMenu. The hook is particularly useful for trapping special keys like F1 for help, or Alt-X to exit. For a procedure to be eligible as a pop-up menu character hook it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with one variable parameter of type word, and variable of type integer, and a variable parameter of type integer. The first parameter indicates which key was pressed. the second parameter indicates which pick is highlighted. If this variable is assigned a non-zero value, the menu will automatically terminate, and the Errorcode (passed to DisplayMenu) will be assigned the value. The following procedure declaration follows these rules: {$F+} procedure CheckForKeys(var Key:word; Choice:integer; var Ecode:integer); {Checks for F1 or Alt-X} begin if Key = 315 then ..... end; { CheckForkeys } {$F-} Having written a procedure following the rules, the menu record field Hook should be set to the procedure, e.g. MainMenu.Hook := CheckForkeys; The demo program DEMMEN4.PAS illustrates how to use a pop-up menu character hook. Structuring a Menu-Driven Program If you are writing an application which uses a Gold classic menu as the main menu, read on. Using a Repeat-Until Loop DisplayMenu displays a menu and returns the value selected by the user. If you want an application to always return to the main menu after completing a task selected by the user, the DisplayMenu statement must be placed in some form of loop. Listed below is an extract from DEMMEN1.PAS which loops continuously back to the main menu: SetScreen; SetMenu; MouseShow(true); Choice := 5; repeat DisplayMenu(MainMenu,false,Choice,ECode); case Choice of 1: PromptOK(' Pretend ','Descriptions'); 2: ; 3: PromptOK(' Pretend ','Self-Running Demo'); 4: PromptOK(' Pretend ','How to register'); 5: PromptOK(' About ','^Gold||Copyr.....'); end; until Choice = 7; MouseShow(false); clrscr; Notice that the initial value of Choice is set before the menu is first displayed, and that the repeat loop is terminated when the user selects option 7 -- the quit option. Also, note that the code includes a case statement after DisplayMenu to branch to another part of the program based on the user's selection. That's all there is to it! Created Nested Menus The following extract from DEMMEN5.PAS illustrates how to create nested menus. In this example, when a user selects item 1 from the main menu, a sub-menu of additional items is displayed. Pressing Esc in the sub-menu will return the user back to the main menu. Choice := 1; SubChoice := 1; repeat DisplayMenu(M1,false,Choice,ECode); case Choice of 1: begin repeat DisplayMenu(MM,true,SubChoice,ECode); if Ecode <> 0 then SubChoice := 5; case SubChoice of 1: ; 2: ; {etc.} end; until SubChoice = 5; end; 2..4: PromptOK(' Selection ','You chose... end; until Choice = 5; MouseShow(false); clrscr; Notice there is a nested case statement to handle separately the main and sub-menu choices, and that different variables are used to track the menu selections: Choice and SubChoice.